﻿using System;
using Honeycomb.Protocol;
using System.Net;
using System.Threading;
using System.IO;
using Honeycomb.Transport;
using Honeycomb.Transport.Ipv4Synchronous;


namespace Honeycomb.Core.Server
{
    public class GridServer
    {
        /// <summary>
        /// Enumerating and unique client identifier.
        /// </summary>
        private int clientId;

        /// <summary>
        /// Instance of the underlying Network Server being used.
        /// </summary>
        private readonly INetworkServer networkServer;

        /// <summary>
        /// Authenticate handler instance.
        /// </summary>
        public AuthenticateHandler AuthenticateHandler { get; set; }

        /// <summary>
        /// Instance of the underlying Protocol Server being used. This abstracts the network connection
        /// and the network protocol.
        /// </summary>
        private ProtocolServer protocolServer;

        /// <summary>
        /// Queue of pending tasks to execute.
        /// </summary>
        // TODO: Don't hard code the task buffer capacity like this.
        private readonly BlockingQueue<TaskBase> pendingTasks = new BlockingQueue<TaskBase>(10000);

        /// <summary>
        /// Constructor.
        /// </summary>
        public GridServer() : this (GridUri.DefaultPort)
        {            
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="listenPort">Port number that the server will listen on.</param>
        public GridServer(int listenPort) : this(new NetworkServer(IPAddress.Any, listenPort))
        {
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="networkServer">INetworkServer instance.</param>
        public GridServer(INetworkServer networkServer)
        {
            this.networkServer = networkServer;

            // Setup authentication.
            AuthenticateHandlerBasic authenticateHandler = new AuthenticateHandlerBasic();
            authenticateHandler.Add("johndoe", "password");
            authenticateHandler.Add("janedoe", "drowssap");
            AuthenticateHandler = authenticateHandler;
        }

        /// <summary>
        /// Starts the Honeycomb Server, listening for connecting clients.
        /// </summary>
        public void Start()
        {
            protocolServer = new ProtocolServer(networkServer);            
            Thread thread = new Thread(RunServer);
            thread.Start();
        }

        /// <summary>
        /// Stops the Honeycomb Server, disconnecting all connected peers.
        /// </summary>
        public void Stop()
        {
            protocolServer.Dispose();

            // TODO: Implement this method.
            // TODO: Abort RunServer thread
            // TODO: Abort All ProcessClient threads
        }

        /// <summary>
        /// Adds a new task for the Grid Server to process.
        /// </summary>
        /// <param name="newTask">New task to process.</param>
        public void AddTask(TaskBase newTask)
        {
            pendingTasks.Enqueue(newTask);
        }

        /// <summary>
        /// Server processing thread that listens and initiates connections to connecting clients. Any client
        /// processing will be done on a separately spawned thread for that particular client.
        /// </summary>
        private void RunServer()
        {
            // TODO: This must be possible to stop by aborting the thread. When we do, we should either keep 
            // or discard existing tasks being processed by peers.

            for (; ; )
            {
                ProtocolPeer peer = protocolServer.AcceptConnection();
                Console.WriteLine("Client connected ({0})", peer.Address);

                Thread thread = new Thread(ProcessClient);
                thread.Start(peer);
            }
        }

        /// <summary>
        /// Client processing thread that spawns for each connected client. This takes care of sending and
        /// receiving messages to/from the client, plus any processing needed.
        /// </summary>
        /// <param name="protocolPeer">The client/peer to process.</param>
        private void ProcessClient(object protocolPeer)
        {
            using (AssemblyManager assemblyManager = new AssemblyManager())
            using (ProtocolPeer peer = (ProtocolPeer)protocolPeer)
            {
                try
                {
                    // Authenticate client.
                    if (!Authenticate(peer))
                        return;

                    // Client authenticated OK, so kickstart the work cycle -> pass assemblies and tasks, and 
                    // receive task responses.
                    int id = ++clientId;

                    for (; ; )
                    {
                        ClientRequest request = peer.ReadAuthenticatedClientRequest();
                        switch (request.MessageType)
                        {
                            case MessageType.TaskRequest:

                                // TODO: Implement task buffering, reposting at disconnect, etc.
                                // TODO: Blocking Queue is no good here, as it even blocks client disconnects?
                                TaskBase nextTask = pendingTasks.Dequeue();

                                // int start = new Random().Next(100000, 200000);
                                // new PrimeTask(start, start + 10000)
                                peer.SendTaskResponse(nextTask);
                                Console.WriteLine("Client {0}: Got TaskRequest, sent TaskResponse.", id);
                                break;

                            case MessageType.AssemblyRequest:
                                peer.SendAssemblyResponse(assemblyManager.GetSerializedAssemblyByName(request.Data));
                                Console.WriteLine("Client {0}: Got AssemblyRequest, sent AssenblyResponse -> {1}", id, request.Data);
                                break;

                            case MessageType.ResultRequest:
                                peer.SendResultResponse();
                                Console.WriteLine("Client {0}: Got ResultRequest, sent ResultResponse.", id);
                                break;
                        }
                    }
                }
                catch (IOException)
                {
                    Console.WriteLine("Client disconnected ({0})", peer.Address);
                }
            }
        }

        /// <summary>
        /// Authenticate client.
        /// </summary>
        /// <param name="peer">Peer to authenticate.</param>
        /// <returns>Boolean, True if the credentials are valid and false otherwise.</returns>
        private bool Authenticate(ProtocolPeer peer)
        {
            // Get credentials.
            NetworkCredential credential = peer.ReadAuthenticateRequest();

            // Determine if end-user is authenticated.
            // TODO: What if AuthenticateHandler is null
            bool authenticated = AuthenticateHandler.Authenticate(credential);

            // Send authentication response back to client.
            peer.SendAuthenticateResponse(authenticated);

            return authenticated;
        }
    }
}
